#pragma once
#include <iostream>
#include <cstring>
enum class BinOp
{
    Plus,
    Minus,
    Times,
    Divide,
};

class Stm;
class Expr
{
public:
    enum class ExprKind
    {
        IdExpr,
        NumExpr,
        OpExpr,
        EseqExpr,
    };

    union ExprU
    {
        char const *id; // c string
        int num;
        struct
        {
            Expr *leftPtr; // 自指时必要的间接引用。 要不要常量引用？
            BinOp opCode;
            Expr *rightPtr;
        } opStruct;
        struct
        {
            Stm *stmPtr;   // statement
            Expr *exprPtr; // 自指时必要的间接引用。通过正常程序构造的不可能为nullptr，但是移动构造后有可能。
        } effectSeqStruct;
    };

    ExprKind kind;
    ExprU unibody;

    // Id构造函数
    Expr(char const *given_id) : kind(ExprKind::IdExpr) // , unibody(given_id)
    {
        this->unibody.id = given_id;
    }
    // Num构造函数
    Expr(int given_num) : kind(ExprKind::NumExpr)
    {
        this->unibody.num = given_num;
    }

    // 复制构造函数
    Expr(Expr const &source) = delete;

    // 移动构造函数 小心移动后使用源对象。
    Expr(Expr &&source)
    {
        this->kind = source.kind;
        switch (source.kind)
        {
        case ExprKind::NumExpr:
        case ExprKind::IdExpr:

            this->unibody = source.unibody;
            break;

        case ExprKind::EseqExpr:

            this->unibody.effectSeqStruct =
                {
                    source.unibody.effectSeqStruct.stmPtr,
                    source.unibody.effectSeqStruct.exprPtr,
                };
            source.unibody.effectSeqStruct.stmPtr = nullptr;
            source.unibody.effectSeqStruct.exprPtr = nullptr;
            // 就是在类似这里的操作使得Expr类的unibody成员不能是const的。
            break;

        case ExprKind::OpExpr:

            this->unibody.opStruct =
                {
                    source.unibody.opStruct.leftPtr,
                    source.unibody.opStruct.opCode,
                    source.unibody.opStruct.rightPtr,
                };
            source.unibody.opStruct.leftPtr = nullptr;
            source.unibody.opStruct.rightPtr = nullptr;
            break;

        default:
            break;
        }
    }
    // Op三参构造函数
    Expr(Expr *givenLeft, BinOp opCode, Expr *givenRight) : kind(ExprKind::OpExpr)
    {
        this->unibody.opStruct =
            {
                givenLeft,
                opCode,
                givenRight,
            };
    }
    // Eseq二参构造
    Expr(Stm *givenStm, Expr *givenExpr) : kind(ExprKind::EseqExpr)
    {
        this->unibody.effectSeqStruct =
            {
                givenStm,
                givenExpr,
            };
    }
    // 复制赋值操作符
    Expr &operator=(Expr const &rhs) = delete;

    // 移动赋值
    Expr &operator=(Expr &&rhs) = delete;
};

class ExprList
{
public:
    enum class ExprListKind
    {
        PairExprList,
        LastExprList
    };

    union ExprListU
    {
        struct
        {
            Expr *head;
            ExprList *tail;
        } pair;
        Expr *last;
    };

    ExprListKind kind;
    ExprListU unibody;

    ExprList(ExprList const &source) = delete;

    ExprList &operator=(ExprList const &rhs) = delete;

    ExprList(Expr *givenLast) : kind(ExprListKind::LastExprList)
    {
        this->unibody.last = givenLast;
    }

    ExprList(Expr *givenHead, ExprList *givenTail) : kind(ExprListKind::PairExprList)
    {
        this->unibody.pair.head = givenHead;
        this->unibody.pair.tail = givenTail;
    }
};

class Stm
{
public:
    enum class StmKind
    {
        CoumpoundStm,
        AssignStm,
        PrintStm,
    };

    union StmU
    {
        struct
        {
            Stm *stm1;
            Stm *stm2;
        } coumpound;
        struct
        {
            char const *id;
            Expr *exprPtr;
        } assign;
        struct
        {
            ExprList *exprLsPtr;
        } print;
    };

    StmKind kind;
    StmU unibody;

    Stm(Stm const &s) = delete;

    Stm &operator=(Stm const &rhs) = delete;

    Stm(char const *gId, Expr *exprPtr) : kind(StmKind::AssignStm)
    {
        this->unibody.assign.id = gId;
        this->unibody.assign.exprPtr = exprPtr;
    }

    Stm(ExprList *exprLsPtr) : kind(StmKind::PrintStm)
    {
        this->unibody.print.exprLsPtr = exprLsPtr;
    }

    Stm(Stm *stm1, Stm *stm2) : kind(StmKind::CoumpoundStm)
    {
        this->unibody.coumpound.stm1 = stm1;
        this->unibody.coumpound.stm2 = stm2;
    }
};

int maxPrintKindArgs(Stm *);
int maxPrintKindArgsInExpr(Expr *expr)
{
    int count = 0;
    switch (expr->kind)
    {
    case Expr::ExprKind::IdExpr:
    case Expr::ExprKind::NumExpr:
        std::cout << "dead end in expr" << std::endl;
        break;
    case Expr::ExprKind::OpExpr:
        std::cout << "found opExpr ";
        count = maxPrintKindArgsInExpr(expr->unibody.opStruct.leftPtr) + maxPrintKindArgsInExpr(expr->unibody.opStruct.rightPtr);
        break;
    case Expr::ExprKind::EseqExpr:
        std::cout << "found eseqExpr ";
        count = maxPrintKindArgs(expr->unibody.effectSeqStruct.stmPtr) + maxPrintKindArgsInExpr(expr->unibody.effectSeqStruct.exprPtr);
        break;
    default:
        break;
    }
    return count;
}

int maxPrintKindArgsInExprList(ExprList *expLsPtr)
{
    int count = 0;
    if (expLsPtr->kind == ExprList::ExprListKind::LastExprList)
    {
        count = maxPrintKindArgsInExpr(expLsPtr->unibody.last);
    }
    else
    {
        count = maxPrintKindArgsInExpr(expLsPtr->unibody.pair.head) + maxPrintKindArgsInExprList(expLsPtr->unibody.pair.tail);
    }
    return count;
}

int maxPrintKindArgs(Stm *stmPtr)
{
    int count = 0;
    // std::cout << stm.getKind() << std::endl;
    switch (stmPtr->kind)
    {
    case Stm::StmKind::PrintStm:
        std::cout << "found printStm! ";
        count = 1 + maxPrintKindArgsInExprList(stmPtr->unibody.print.exprLsPtr);
        break;
    case Stm::StmKind::AssignStm:
        std::cout << "found assign! ";
        count = maxPrintKindArgsInExpr(stmPtr->unibody.assign.exprPtr);
        break;
    case Stm::StmKind::CoumpoundStm:
        std::cout << "found compound! ";
        count = maxPrintKindArgs(stmPtr->unibody.coumpound.stm1) + maxPrintKindArgs(stmPtr->unibody.coumpound.stm2);
        break;
    default:
        std::cout << "no match ";
        break;
    }
    return count;
}

class SymbolTable
{
public:
    char const *id;
    int value;
    SymbolTable *tailPtr;

    SymbolTable(char const *id, int value, SymbolTable *tailPtr) : id(id), value(value), tailPtr(tailPtr) {}

    static int lookup(SymbolTable *sTaPtr, char const *id)
    {
        while (sTaPtr != nullptr)
        {
            if (!strcmp(sTaPtr->id, id))
                return sTaPtr->value;

            sTaPtr = sTaPtr->tailPtr;
        }
        return -1; //应该用result<t, error>
    }

    // 主要是c++和c语言完全不一样，c++要用malloc势必要使用raii然后改写几个析构造函数。

    static SymbolTable *update(SymbolTable *old, char const *id, int new_val)
    {
        SymbolTable *sT = new SymbolTable(id, new_val, old);
        return sT;
    }
};

class IntAndTable
{

public:
    int i;
    SymbolTable *tPtr;
    IntAndTable(int ival, SymbolTable *tP) : i(ival), tPtr(tP) {}
};

IntAndTable interpExpr(Expr *e, SymbolTable *t);
SymbolTable *printExprList(ExprList *elPtr, SymbolTable *taPtr);
SymbolTable* interpStm(Stm *stm, SymbolTable *taPtr)
{
    switch (stm->kind)
    {
    case Stm::StmKind::PrintStm:
        // 可能有问题
        // 只要printExprList内部newSymbolTable*就问题不大
        taPtr = printExprList(stm->unibody.print.exprLsPtr, taPtr);
        break;
    case Stm::StmKind::AssignStm:
    {
        // 这要求interpExpr内要new
        IntAndTable result = interpExpr(stm->unibody.assign.exprPtr, taPtr);
        taPtr = result.tPtr;
        taPtr = SymbolTable::update(taPtr, stm->unibody.assign.id, result.i);
    }
    break;
    case Stm::StmKind::CoumpoundStm:
        taPtr = interpStm(stm->unibody.coumpound.stm1, taPtr);
        taPtr = interpStm(stm->unibody.coumpound.stm2, taPtr);
        break;
    default:
        break;
    }
    return taPtr;
}

SymbolTable *printExprList(ExprList *elPtr, SymbolTable *taPtr)
{
    SymbolTable sT = SymbolTable("", 0, nullptr);
    switch (elPtr->kind)
    {
    case ExprList::ExprListKind::LastExprList:
    {
        // interpExpr new了新table
        IntAndTable result = interpExpr(elPtr->unibody.last, taPtr);
        taPtr = result.tPtr;
        std::cout << result.i << " ";
    }
    break;
    case ExprList::ExprListKind::PairExprList:
    {
        IntAndTable result = interpExpr(elPtr->unibody.pair.head, taPtr);
        taPtr = result.tPtr;
        std::cout << result.i << " ";
        taPtr = printExprList(elPtr->unibody.pair.tail, taPtr);
    }
    break;
    default:
        break;
    }
    return taPtr;
}

IntAndTable interpExpr(Expr *e, SymbolTable *t)
{
    IntAndTable result = IntAndTable(0, nullptr);
    switch (e->kind)
    {
    case Expr::ExprKind::EseqExpr:
        t = interpStm(e->unibody.effectSeqStruct.stmPtr, t);
        result = interpExpr(e->unibody.effectSeqStruct.exprPtr, t);
        break;
    case Expr::ExprKind::IdExpr:
    {
        int value = SymbolTable::lookup(t, e->unibody.id);
        // std::cout << value << " ";
        result = IntAndTable(value, t);
    }
    break;
    case Expr::ExprKind::NumExpr:
        // std::cout << e->getU().num << " ";
        result = IntAndTable(e->unibody.num, t);
        break;
    case Expr::ExprKind::OpExpr:
    {
        switch (e->unibody.opStruct.opCode)
        {
        case BinOp::Plus:
        {
            IntAndTable r1 = interpExpr(e->unibody.opStruct.leftPtr, t);
            IntAndTable r2 = interpExpr(e->unibody.opStruct.rightPtr, r1.tPtr);
            result = IntAndTable(r1.i + r2.i, r2.tPtr);
        }
        break;
        case BinOp::Minus:
        {
            IntAndTable r1 = interpExpr(e->unibody.opStruct.leftPtr, t);
            IntAndTable r2 = interpExpr(e->unibody.opStruct.rightPtr, r1.tPtr);
            result = IntAndTable(r1.i - r2.i, r2.tPtr);
        }
        break;
        case BinOp::Times:
        {
            IntAndTable r1 = interpExpr(e->unibody.opStruct.leftPtr, t);
            IntAndTable r2 = interpExpr(e->unibody.opStruct.rightPtr, r1.tPtr);
            result = IntAndTable(r1.i * r2.i, r2.tPtr);
        }
        break;
        case BinOp::Divide:
        {
            IntAndTable r1 = interpExpr(e->unibody.opStruct.leftPtr, t);
            IntAndTable r2 = interpExpr(e->unibody.opStruct.rightPtr, r1.tPtr);
            result = IntAndTable(r1.i / r2.i, r2.tPtr);
        }
        break;
        default:
            break;
        }
    }
    break;
    default:
        break;
    }
    return result;
}